---
title: Authorize a user for a role, trying the internal domain first
summary: IsAuthorized end-to-end process on SAIF.Services.WCF.Services.Security.SecurityService.
status: draft
owner: SAIF Doc Gen
last_reviewed: 2026-09-22
operation_id: OP-015
trigger: Caller invokes `IsAuthorized` over the `Security` route.
---

# OP-015: Authorize a user for a role, trying the internal domain first

IsAuthorized is invoked over the `Security` route on `SAIF.Services.WCF.Services.Security.SecurityService`; this page documents the process end to end, from route dispatch to the message the caller receives.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-015 |
| **Trigger** | Caller invokes `IsAuthorized` over the `Security` route. |
| **Entry operation** | [`IsAuthorized`](../contracts/security-service.md#isauthorized) |
| **Owning component** | `SAIF.Services.WCF.Services.Security.SecurityService.IsAuthorized` |
| **Inputs** | Request message parts listed below |
| **Outputs** | Whether `userId` is authorized for `role`. |
| **Failure behavior** | Generic SOAP fault, per [Faults and error pipeline](../faults.md) |

## Request and response

| Direction | Element | Type | Declared in | Notes |
| --- | --- | --- | --- | --- |
| Request | `userId` | `string` | BCL (`System`) | Account to check; validated non-blank. (Yes) |
| Request | `role` | `string` | BCL (`System`) | Role/group name to check; validated non-blank. (Yes) |
| Response | `bool` | `bool` | BCL (`System`) | Whether `userId` is authorized for `role`. |

<!-- Types declared in `SAIF.Services.WCF.Contracts` are attributed to `SAIF.Framework.Core`, inferred from source and package references and marked `*`; their members are not enumerated because the external declaration was not read, marked `**`. -->

## Steps

1. Constructor reads `LOG_REQUEST_DETAILS` and `LDAP_EXTERNAL_KEY` from `appSettings` inside an empty `catch`, resolves the external LDAP host from the environment with a literal fallback, opens the Oracle `EXTUR` unit of work, constructs `UserFactory`, and calls `AuthorizationCacheService.Init` when the cache is not ready ([BR-WCF-018](../business-rules.md#br-wcf-018-external-ldap-host-falls-back-to-a-literal-default), [BR-WCF-029](../business-rules.md#br-wcf-029-authorization-answers-are-cached-per-user-role-and-domain)).
2. `LogRequestDetails` appends the operation name and its arguments to the IIS log ([BR-WCF-032](../business-rules.md#br-wcf-032-every-security-operation-logs-its-request)).
3. `IsAuthorized` delegates to `IsAuthorized2(userId, role, true)` ([BR-WCF-030](../business-rules.md#br-wcf-030-internal-domain-is-tried-before-external-domain)).
4. `Enforce.ArgumentNotNullOrWhiteSpace` on `userId` and `role`; the third guard passes `role` under the label `"domain"`, so `domain` is never actually validated ([BR-WCF-023](../business-rules.md#br-wcf-023-required-arguments-are-presence-checked-before-work)).
5. Because `tryInternalDomainFirst` is `true`, `IsAuthorizedInternal` asks `AuthorizationCacheService.IsAuthorized` for the `Internal` domain; on a cache miss it constructs a `WindowsIdentity` for `{userId}@corp.saif.com`, confirms the returned SAM name matches case-insensitively, and evaluates `WindowsPrincipal.IsInRole(role)` ([BR-WCF-030](../business-rules.md#br-wcf-030-internal-domain-is-tried-before-external-domain)). A `SecurityException` indicating a disabled account returns `false`; one indicating a bad user name or password falls through to the external check; anything else rethrows ([BR-WCF-031](../business-rules.md#br-wcf-031-disabled-accounts-are-never-authorized)).
6. When the internal answer is `false`, `IsAuthorizedExternal` asks the cache for the `External` domain; on a miss it opens a `PrincipalContext` against the mutable `ldap_external` field, resolves the group and the user, returns `false` for a missing group, a missing user, or an explicitly disabled account, and otherwise evaluates `user.GetAuthorizationGroups().Contains(group)` ([BR-WCF-030](../business-rules.md#br-wcf-030-internal-domain-is-tried-before-external-domain), [BR-WCF-031](../business-rules.md#br-wcf-031-disabled-accounts-are-never-authorized)).
7. `AuthorizationCacheService.IsAuthorized` caches the result in `HttpContext.Current.Cache` under a hash of `{user}@{domain}/{role}` with the per-domain TTL, absolute expiration, `NoSlidingExpiration`, `Normal` priority ([BR-WCF-029](../business-rules.md#br-wcf-029-authorization-answers-are-cached-per-user-role-and-domain)). With no `HttpContext`, the cache is bypassed entirely in both directions.
8. Return the boolean.
   - On failure: directory exceptions other than the two handled `SecurityException` shapes are logged by the installed error handler and surfaced as a generic SOAP fault with exception detail included.

## Process flow

The following diagram visualizes this process's control flow.

```mermaid
flowchart TD
    A["Initialize dependencies and<br>authorization cache"]
    B["Log request details"]
    C["Delegate to IsAuthorized2<br>(try internal domain first)"]
    D["Validate userId and role"]
    E["Check internal-domain<br>authorization cache"]
    E1{"Internal domain: cache hit?"}
    E2["Cache miss: check Windows role<br>membership via WindowsIdentity"]
    E3{"SecurityException raised?"}
    E4["Disabled account: return false"]
    E5["Bad user name or password:<br>fall through to the external check"]
    E6["Anything else: rethrow"]
    E7{"Internal answer"}
    F["Check external cache; on miss,<br>resolve AD user/group and account status"]
    G["Cache the authorization result"]
    H["Return the boolean."]
    A --> B
    B --> C
    C --> D
    D --> E
    E --> E1
    E1 -->|"Yes"| E7
    E1 -->|"No"| E2
    E2 --> E3
    E3 -->|"Disabled account"| E4
    E3 -->|"Bad user name or password"| E5
    E3 -->|"Other"| E6
    E5 --> F
    E4 --> F
    E2 -->|"No exception"| E7
    E7 -->|"true"| G
    E7 -->|"false"| F
    F --> G
    G --> H
```

## Transaction and side effects

| Aspect | Finding |
| --- | --- |
| Unit of work | `OracleUnitOfWorkFactory` (`EXTUR`), opened but not used for this decision path |
| Commit point | Not applicable |
| Records written | None for this path |
| Downstream calls | Active Directory (`corp.saif.com` and the external LDAP host); ASP.NET `HttpContext.Current.Cache` |
| Shared state touched | Reads and, on a cache miss, mutates the process-wide `AuthorizationCacheService` cache and the mutable `ldap_external` field |
| Repeat-call behavior | A cached result short-circuits the directory calls for the TTL window |

## Logging

| Surface | Written content |
| --- | --- |
| `HttpContext.Current.Response.AppendToLog` (`LogRequestDetails`) | Writes IIS request-detail log lines when `LOG_REQUEST_DETAILS` is enabled; populates the shared authorization cache |
| `ErrorLogErrorHandler` → `LogService` | Failure-path exception logging only, carrying method signature, calling user and exception detail; see [Faults and error pipeline](../faults.md) |

## Faults and error handling

| Failure | Fault type the caller receives | Produced by | Source |
| --- | --- | --- | --- |
| `SecurityException` (uncategorized) | `Generic SOAP fault` | The `ErrorLogErrorHandler` installed by `[LogErrorBehavior]` | src/WCF/SAIF.Services.WCF.Host/Services/Security/SecurityService.cs |
| Directory or Oracle/NHibernate exception | `Generic SOAP fault` | The `ErrorLogErrorHandler` installed by `[LogErrorBehavior]` | src/WCF/SAIF.Services.WCF.Host/Services/Security/SecurityService.cs |

## Authorization

| Requirement | Enforced by | Source | Applies to |
| --- | --- | --- | --- |
| [None in code](../authentication-authorization.md#authorization-enforcement) | IIS authentication on the deployment (see [Activation and hosting](../activation-hosting.md)) | `src/WCF/SAIF.Services.WCF.Host/Services/Security/SecurityService.cs` | This operation |

## Related documentation
- [Index](../index.md)
- [Overview](../overview.md)
- [Architecture](../architecture.md)
- [Activation and hosting](../activation-hosting.md)
- [Operations](../operations.md)
- [Faults and error pipeline](../faults.md)
- [Authentication and authorization](../authentication-authorization.md)
- [Dependencies](../dependencies.md)
- [Configuration](../configuration.md)
- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)
- [Data model](../data-model.md)
- [Operational runbook](../operational-runbook.md)
- [Tech debt](../tech-debt.md)

> **Note:** Statements marked with `*` were inferred from code structure, naming
> conventions, or contextual evidence where direct confirmation was not available
> in source artifacts. Statements marked with `**` could not be confirmed; treat
> as unknown until verified.

## Sources
- src/WCF/SAIF.Services.WCF.Host/Services/Security/SecurityService.cs
